// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MemoryTrace.cs" company="AzureContrib (Open source library http://azurecontrib.codeplex.com)">
//   Microsoft Public License (Ms-PL)
// </copyright>
// <summary>
//   Creates <see cref="ITrace"/> which writes to memory.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;

namespace AzureContrib.WindowsAzure.Diagnostics.Testing
{
	/// <summary>
	/// Creates <see cref="ITrace"/> which writes to memory.
	/// </summary>
	/// <remarks>>Use this for testing: Please note that this class will eat your memory! It is a useful way to track tracing in run time but left unguarded this will eat your memory!</remarks>
	public class MemoryTrace : Trace, IDisposable
	{
		private MemoryTraceListener memoryTraceListener;

		private bool disposed;

		/// <summary>
		/// Initializes a new instance of the <see cref="MemoryTrace"/> class.
		/// </summary>
		/// <param name="toTraceFor">The type we are tracing for.</param>
		/// <param name="traceSwitchProvider">The current <see cref="ITraceSwitchProvider"/>.</param>
		public MemoryTrace(Type toTraceFor, ITraceSwitchProvider traceSwitchProvider)
			: base(toTraceFor, traceSwitchProvider)
		{
			Init(true);
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="MemoryTrace"/> class.
		/// </summary>
		/// <param name="toTraceFor">The type we are tracing for.</param>
		/// <param name="traceSwitchProvider">The current <see cref="ITraceSwitchProvider"/>.</param>
		/// <param name="onlyValidTraceEntries">True if only messages traced through the use of <see cref="ITrace"/> should be considered.</param>
		public MemoryTrace(Type toTraceFor, ITraceSwitchProvider traceSwitchProvider, bool onlyValidTraceEntries)
			: base(toTraceFor, traceSwitchProvider)
		{
			Init(onlyValidTraceEntries);
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="MemoryTrace"/> class.
		/// </summary>
		/// <param name="switch">The switch name we are tracing for.</param>
		/// <param name="traceSwitchProvider">The current <see cref="ITraceSwitchProvider"/>.</param>
		public MemoryTrace(string @switch, ITraceSwitchProvider traceSwitchProvider)
			: base(@switch, traceSwitchProvider)
		{
			Init(true);
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="MemoryTrace"/> class.
		/// </summary>
		/// <param name="switch">The switch name we are tracing for.</param>
		/// <param name="traceSwitchProvider">The current <see cref="ITraceSwitchProvider"/>.</param>
		/// <param name="onlyValidTraceEntries">True if only messages traced through the use of <see cref="ITrace"/> should be considered.</param>
		public MemoryTrace(string @switch, ITraceSwitchProvider traceSwitchProvider, bool onlyValidTraceEntries)
			: base(@switch, traceSwitchProvider)
		{
			Init(onlyValidTraceEntries);
		}

		/// <summary>
		/// Gets the traces collected.
		/// </summary>
		public IEnumerable<TraceMessage> Traces
		{
			get { return memoryTraceListener.Traces; }
		}

		/// <summary>
		/// Clear the traces collected until now.
		/// </summary>
		public void ClearTrace()
		{
			memoryTraceListener.ClearTrace();
		}

		/// <summary>
		/// Finds out if an exact trace Message has been traced to the <see cref="MemoryTrace"/>.
		/// </summary>
		/// <param name="traceMessage">The Message to find</param>
		/// <returns>True if the <paramref name="traceMessage" /> has been traced.</returns>
		public bool AnyExactTraceExists(string traceMessage)
		{
			return AnyExactTraceExists(traceMessage, null);
		}

		/// <summary>
		/// Finds out if an exact trace Message has been traced to the <see cref="MemoryTrace"/>.
		/// </summary>
		/// <param name="traceMessage">The Message to find</param>
		/// <param name="switch">An optional switch it was traced to.</param>
		/// <returns>True if the <paramref name="traceMessage" /> has been traced.</returns>
		public bool AnyExactTraceExists(string traceMessage, string @switch)
		{
			return Traces.Any(tm => tm.Message == traceMessage && 
												(@switch == null || tm.Switch == @switch));
		}

		/// <summary>
		/// Finds out if an exact trace Message has been traced to the <see cref="MemoryTrace"/>.
		/// </summary>
		/// <param name="traceMessage">The Message to find</param>
		/// <param name="level">A level the trace has to be traced to.</param>
		/// <returns>True if the <paramref name="traceMessage" /> has been traced.</returns>
		public bool AnyExactTraceExists(string traceMessage, LogLevel level)
		{
			return AnyExactTraceExists(traceMessage, level, null);
		}

		/// <summary>
		/// Finds out if an exact trace Message has been traced to the <see cref="MemoryTrace"/>.
		/// </summary>
		/// <param name="traceMessage">The Message to find</param>
		/// <param name="level">A level the trace has to be traced to.</param>
		/// <param name="switch">An optional switch it was traced to.</param>
		/// <returns>True if the <paramref name="traceMessage" /> has been traced.</returns>
		public bool AnyExactTraceExists(string traceMessage, LogLevel level, string @switch)
		{
			return Traces.Any(tm => tm.Message == traceMessage && 
			                        tm.Level == level.ToString() && 
			                        (@switch == null || tm.Switch == @switch));
		}

		/// <summary>
		/// Finds out if exactly one specific trace Message has been traced to the <see cref="MemoryTrace"/>.
		/// </summary>
		/// <param name="traceMessage">The Message to find</param>
		/// <returns>True if the <paramref name="traceMessage" /> has been traced.</returns>
		public bool OneExactTraceExists(string traceMessage)
		{
			return OneExactTraceExists(traceMessage, null);
		}

		/// <summary>
		/// Finds out if exactly one specific trace Message has been traced to the <see cref="MemoryTrace"/>.
		/// </summary>
		/// <param name="traceMessage">The Message to find</param>
		/// <param name="switch">An optional switch it was traced to.</param>
		/// <returns>True if the <paramref name="traceMessage" /> has been traced.</returns>
		public bool OneExactTraceExists(string traceMessage, string @switch)
		{
			return Traces.SingleOrDefault(tm => tm.Message == traceMessage &&
			                                    ((@switch == null) || (tm.Switch == @switch))) != null;
		}

		/// <summary>
		/// Finds out if exactly one specific trace Message has been traced to the <see cref="MemoryTrace"/>.
		/// </summary>
		/// <param name="traceMessage">The Message to find</param>
		/// <param name="level">A level the trace has to be traced to.</param>
		/// <returns>True if the <paramref name="traceMessage" /> has been traced.</returns>
		public bool OneExactTraceExists(string traceMessage, LogLevel level)
		{
			return OneExactTraceExists(traceMessage, level, null);
		}

		/// <summary>
		/// Finds out if exactly one specific trace Message has been traced to the <see cref="MemoryTrace"/>.
		/// </summary>
		/// <param name="traceMessage">The Message to find</param>
		/// <param name="level">A level the trace has to be traced to.</param>
		/// <param name="switch">An optional switch it was traced to.</param>
		/// <returns>True if the <paramref name="traceMessage" /> has been traced.</returns>
		public bool OneExactTraceExists(string traceMessage, LogLevel level, string @switch)
		{
			try
			{
				return Traces.SingleOrDefault(tm => tm.Message == traceMessage &&
				                                    tm.Level == level.ToString() &&
				                                    (@switch == null || tm.Switch == @switch)) != null;
			}
			catch (InvalidOperationException ioe)
			{
				if (ioe.Message.Equals("Sequence contains more than one matching element"))
				{
					throw new InvalidOperationException("There was more than one trace traced with the Message <{0}>, the Log Level <{1}> and the switch <{2}>.".F(traceMessage, level, @switch), ioe);
				}

				throw;
			}
		}

		/// <summary>
		/// Dispose the <see cref="System.Diagnostics.TraceListener"/> used by this instance.
		/// </summary>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Dispose the resources of this instance.
		/// </summary>
		/// <param name="disposing">True if call is from the instance and false if it is from the destructor.</param>
		protected virtual void Dispose(bool disposing)
		{
			if (disposed)
			{
				return;
			}

			if (disposing)
			{
				memoryTraceListener.Dispose();
			}

			disposed = true;
		}

		private void Init(bool onlyValidTraceEntries)
		{
			memoryTraceListener = new MemoryTraceListener(onlyValidTraceEntries);
		}

		private class MemoryTraceListener : System.Diagnostics.TraceListener
		{
			private readonly List<string> traces = new List<string>();
			private readonly bool onlyValidTraceEntries;

			public MemoryTraceListener(bool onlyValidTraceEntries = true)
			{
				this.onlyValidTraceEntries = onlyValidTraceEntries;
				System.Diagnostics.Trace.Listeners.Add(this);
			}

			public IEnumerable<TraceMessage> Traces
			{
				get
				{
					return traces.AsReadOnly().Select(trace => (TraceMessage)trace);
				}
			}

			public override void Write(string message)
			{
				DoAddTrace(message);
			}

			public override void WriteLine(string message)
			{
				DoAddTrace(message);
			}

			public void ClearTrace()
			{
				traces.Clear();
			}

			protected override void Dispose(bool disposing)
			{
				base.Dispose(disposing);
				System.Diagnostics.Trace.Listeners.Remove(this);
			}

			private void DoAddTrace(string message)
			{
				if (!onlyValidTraceEntries ||
						TraceMessage.IsValidMessage(message))
				{
					traces.Add(message);
				}
			}
		}
	}
}